#!/bin/bash

#########################################################
SUCCEEDED=0
ERR_NO_CFGFILE=1
ERR_PARAM_WRONG=2
ERR_LINE_FORMAT=3
ERR_IMG2SIMG_NOT_FOUND=4
ERR_SOURCE_NAME_INVALID=5
ERR_UNIT_IS_INVALID=6
ERR_INPUT_IS_INVALID=7
ERR_PARAM_NUM=8
ERR_NO_INPUT_PRG_FILE=9
GLOBAL_SECTION_HAS_PROCESSED=10
ERR_CKSFV_NOT_FOUND=11
#########################################################

if ! which img2simg >/dev/null 2>&1; then
	echo "Warning: img2simg not found!"
	exit ${ERR_IMG2SIMG_NOT_FOUND}
fi

if ! which cksfv >/dev/null 2>&1; then
	echo "Warning: cksfv not found!"
	exit ${ERR_CKSFV_NOT_FOUND}
fi

##########################################################
GLOBAL_FORMAT_BIT_MAX=6
declare -A global_format_dict
global_format_dict=()
##########################################################

####################func start############################
print_error()
{
	local ERROR_STR=$1
	echo "Error: ${ERROR_STR}";exit $2
}

print_notice()
{
	local NOTICE_STR=$1
	echo "Notice: ${NOTICE_STR}";exit ${SUCCEEDED}
}
## endian_swap_8bit function
# $1: hex string with 8 char number
#   For example: '000009E6'
# OUT: hex string with 8 char number
#   For example: 'E6090000'
# ret: 0  - succeed
#      !0 - fail
## If not an 8-bit number, output directly
endian_swap_8bit()
{
	# Check parameter number
	if [ 1 -ne $# ]; then
		return 1
	fi

	# Check whether parameter is hex string with 8 char number
	if ! (echo $1 | grep '^[0-9A-Fa-f]\{8\}$') >/dev/null 2>&1; then
		#output directly
		echo -n $1
	else
		# Change to little endian and output
		echo -n `echo -n $1 | cut -c 7-8``echo -n $1 | cut -c 5-6``echo -n $1 | cut -c 3-4``echo -n $1 | cut -c 1-2`
	fi
}

## echo_hex function
# $1: hex string, char number must be even
#     For example: 'A55A5AA5'
# OUT: binary stream of hex string
# ret: 0  - succeed
#      !0 - fail
echo_hex()
{
	# Check parameter number
	if [ 1 -ne $# ]; then
		return 1
	fi

	# Check whether parameter is hex string
	if ! (echo $1 | grep '^[0-9A-Fa-f]\+$') >/dev/null 2>&1; then
		return 2
	fi

	local CHAR_NUM=`echo -n $1 | wc -c`
	# Check char number of hex string
	if [ 0 -ne $((${CHAR_NUM} % 2)) ]; then
		return 3
	fi
	echo -en "`echo $1 | sed 's/../\\\x&/g'`"
}
## digit_unit_split function
# $1: input with unit
#     Unit: c/B(1), w(2B), b/s(512B), KB(1000B), K/KiB(1024B), MB(1000KB), M/MiB(1024K), GB(1000MB), G/GiB(1024M), TB(1000GB), T/TiB(1024G), not support PB, P, EB, E, ZB, Z, YB, Y
# ret: digit & unit
#      if error, echo nothing
#
# for example:
#   digit_unit_split 1024B --> 1024 B
digit_unit_split()
{
	if [ 1 -ne $# ]; then
		return ${ERR_PARAM_NUM}
	fi

	local INPUT=$1

	# Input parameter check
	if ! (echo ${INPUT} | grep '^[[:digit:]]\+\([cBwbsKMGT]\|KB\|KiB\|MB\|MiB\|GB\|GiB\|TB\|TiB\)$') >/dev/null 2>&1; then
		return ${ERR_INPUT_IS_INVALID}
	fi

	# Split the digit & unit
	echo `echo ${INPUT} | grep -o '^[[:digit:]]\+'` `echo ${INPUT} | grep -o '\([cBwbsKMGT]\|KB\|KiB\|MB\|MiB\|GB\|GiB\|TB\|TiB\)$'`
}

## unit_convert function
# $1: input with unit
#     Unit: refer to digit_unit_split()
# $2: output unit
#     Unit: refer to digit_unit_split()
# ret: output how many bytes
#      if error, echo nothing
#
# for example:
#   unit_convert 1024B K --> 1
#   unit_convert 1s B --> 512
#   unit_convert 1s K --> nothing
#   unit_convert 1KiB KB --> nothing
#
# Note: this function doesn't support float point arithmetic
unit_convert()
{
	if [ 2 -ne $# ]; then
		return ${ERR_PARAM_NUM}
	fi

	local INPUT=`digit_unit_split $1 | awk '{print $1}'`
	local INPUT_UNIT=`digit_unit_split $1 | awk '{print $2}'`
	local OUTPUT_UNIT=$2
	local OUTPUT=""

	if [ -z "${INPUT}" -o -z "${INPUT_UNIT}" ]; then
		return ${ERR_INPUT_IS_INVALID}
	fi

	# At first, convert INPUT to bytes
	case ${INPUT_UNIT} in
		c|B)
			;;
		w)
			INPUT=$((${INPUT} * 2))
			;;
		b|s)
			INPUT=$((${INPUT} * 512))
			;;
		KB)
			INPUT=$((${INPUT} * 1000))
			;;
		K|KiB)
			INPUT=$((${INPUT} * 1024))
			;;
		MB)
			INPUT=$((${INPUT} * 1000 * 1000))
			;;
		M|MiB)
			INPUT=$((${INPUT} * 1024 * 1024))
			;;
		GB)
			INPUT=$((${INPUT} * 1000 * 1000 * 1000))
			;;
		G|GiB)
			INPUT=$((${INPUT} * 1024 * 1024 * 1024))
			;;
		TB)
			INPUT=$((${INPUT} * 1000 * 1000 * 1000 * 1000))
			;;
		T|TiB)
			INPUT=$((${INPUT} * 1024 * 1024 * 1024 * 1024))
			;;
		*)
			return ${ERR_UNIT_IS_INVALID}
			;;
	esac

	# Then, convert INPUT to OUTPUT
	local DIVIDER=1
	case ${OUTPUT_UNIT} in
		c|B)
			DIVIDER=1
			;;
		w)
			DIVIDER=2
			;;
		b|s)
			DIVIDER=512
			;;
		KB)
			DIVIDER=1000
			;;
		K|KiB)
			DIVIDER=1024
			;;
		MB)
			DIVIDER=1000 * 1000
			;;
		M|MiB)
			DIVIDER=1024 * 1024
			;;
		GB)
			DIVIDER=1000 * 1000 * 1000
			;;
		G|GiB)
			DIVIDER=1024 * 1024 * 1024
			;;
		TB)
			DIVIDER=1000 * 1000 * 1000 * 1000
			;;
		T|TiB)
			DIVIDER=1024 * 1024 * 1024 * 1024
			;;
		*)
			return ${ERR_UNIT_IS_INVALID}
			;;
	esac
	OUTPUT=$((${INPUT} / ${DIVIDER}))
	# Verify wether there's remainder
	if [ ${INPUT} != $((${OUTPUT} * ${DIVIDER})) ]; then
		return ${ERR_UNIT_IS_INVALID}
	fi

	echo ${OUTPUT}
}

#write_ff file size
GLOBAL_BLOCK_SIZE=""
write_ff()
{
	local filename="$1"
	local size="$2"

	if [ -z ${size} ]; then
		return ${ERR_PARAM_WRONG}
	fi

	local block_size=${GLOBAL_BLOCK_SIZE}
	local block_counts=`expr ${size} / ${block_size}`
	local remaining_size=`expr ${size} % ${block_size}`

	tr '\000' '\377' < /dev/zero | dd of=${filename} bs=${block_size} count=${block_counts} oflag=append conv=notrunc iflag=fullblock
	#dd if=/dev/zero bs=${block_size} count=${block_counts} | sed 's/\x00/\xff/g' >> ${filename}
	if [ ${remaining_size} -gt 0 ]; then
		echo -e "remaining_size=${remaining_size}"
		tr '\000' '\377' < /dev/zero | dd of=${filename} bs=${remaining_size} count=1 oflag=append conv=notrunc iflag=fullblock
	fi
}
## get_checksum function
# $1: input file name
#     For example: 'bsp_info.tmp'
get_checksum()
{
	echo_hex `endian_swap_8bit $(cksfv "$1" | grep -v '^;' | awk '{print $2}')`
}
## copy the first XXX byte of input file to the end of output file
# arg1 input file
# arg2 output file
# arg3 copy size
copy_header()
{
	local input=$1
	local output=$2
	local size=$3

	if [ ! -f "${input}" ]; then
		echo "can not find input file ${input},set ff!"
		write_ff ${output} ${size};return ${ERR_NO_INPUT_PRG_FILE}
	fi

	if [ $size -lt 1024 ]; then
		dd if=${input} of=${output} bs=${size} count=1 oflag=append conv=notrunc
	else
		exit ${ERR_PARAM_WRONG}
	fi
}
####################func end############################

IS_GLOBAL_PROCESSED=false
GLOBAL_FILE_PREFIX=""
GLOBAL_SWAP_FILEA=""
GLOBAL_FILE=""
process_global()
{
	eval "GLOBAL_$@"

	# If got all variables
	if [ -n "${GLOBAL_FILE}" -a -n "${GLOBAL_BLOCK_SIZE}" ]; then
		if [ "true" = "${IS_GLOBAL_PROCESSED}" ]; then
			return ${GLOBAL_SECTION_HAS_PROCESSED}
		fi

		GLOBAL_MODULE_SIZE=$(unit_convert ${GLOBAL_MODULE_SIZE} B)
		GLOBAL_FILE_PREFIX="`echo ${GLOBAL_FILE} | sed 's/\.[^\.\/]*$//'`"
		GLOBAL_SWAP_FILEA="${GLOBAL_FILE_PREFIX}_swapA"
		IS_GLOBAL_PROCESSED=true
	fi
}
# process source
process_source()
{
	eval "$@"
}

make_camera_header()
{
	local LOCAL_HEADER_FILE="camerainfo_header_$$"
	local LOCAL_SOURCE="$1"
	local LOCAL_DDR_ADRESS="$2"
	local BLOCK_LEN_BYTE="$3"
	local LOCAL_SOURCE_SIZE=$(printf "%08x" $(stat -c "%s" ${LOCAL_SOURCE}))
	local LOCAL_LZ4_FILE_SIZE LOCAL_CRCC_HEADER

	#lz4 file size
	${GLOBAL_MTK_LZ4} -c1 -l ${LOCAL_SOURCE} > ${LOCAL_SOURCE}.lz4;LOCAL_LZ4_FILE_SIZE=$(printf "%08x" $(stat -c "%s" ${LOCAL_SOURCE}.lz4))
	#data crc
	get_checksum ${LOCAL_SOURCE}.lz4 > ${GLOBAL_SWAP_FILEA}
	#lz40
	echo_hex "6C7A3400" >> ${GLOBAL_SWAP_FILEA}
	#source picture size
	echo_hex `endian_swap_8bit ${LOCAL_SOURCE_SIZE}` >> ${GLOBAL_SWAP_FILEA}
	#compression picture size
	echo_hex `endian_swap_8bit ${LOCAL_LZ4_FILE_SIZE}` >> ${GLOBAL_SWAP_FILEA}
	#ddr address
	echo_hex `endian_swap_8bit ${LOCAL_DDR_ADRESS}` >> ${GLOBAL_SWAP_FILEA}
	#picture offset address
	echo_hex `endian_swap_8bit ${GLOBAL_CAMERAPIC_OFF_ADDRESS}` >> ${GLOBAL_SWAP_FILEA}
	#header crc and make 512 header
	get_checksum ${GLOBAL_SWAP_FILEA} > ${LOCAL_HEADER_FILE};cat ${GLOBAL_SWAP_FILEA} >> ${LOCAL_HEADER_FILE};write_ff ${LOCAL_HEADER_FILE} $((BLOCK_LEN_BYTE - $(stat -c "%s" ${LOCAL_HEADER_FILE})))
	cat ${LOCAL_HEADER_FILE} >> ${GLOBAL_FILE};rm ${LOCAL_HEADER_FILE} ${GLOBAL_SWAP_FILEA}
	echo -e "\e[1;36mCUR_CAMERAPIC_OFF_ADDRESS = ${GLOBAL_CAMERAPIC_OFF_ADDRESS}\e[0m"
	GLOBAL_CAMERAPIC_OFF_ADDRESS=`printf "%08x" $((0x$GLOBAL_CAMERAPIC_OFF_ADDRESS + 0x$LOCAL_LZ4_FILE_SIZE))`
	echo -e "\e[1;36mpic size = ${LOCAL_LZ4_FILE_SIZE},next pic off = ${GLOBAL_CAMERAPIC_OFF_ADDRESS}\e[0m"
}

########################################################################################################
# process_ format_ As a processing function,
# it will be called when parsing the travel format,
# and the parameter is the original filling content in the format
process_format_str()
{
	echo -n "$1" >> "${GLOBAL_FILE}"
}
process_format_input_val()
{
	echo_hex `endian_swap_8bit $(eval $1)` >> "${GLOBAL_FILE}"
}
process_format_str_input_val()
{
	echo -en "$1" >> "${GLOBAL_FILE}"
	echo_hex `endian_swap_8bit $(eval $2)` >> "${GLOBAL_FILE}"
}
process_format_source()
{
	local LOCAL_SOURCE="$1"
	local LEN_TO_BYTE=$(unit_convert $2 B)

	if [ "versioninfo" = "$3" ]; then
		copy_header "$1" "${GLOBAL_FILE}" "${LEN_TO_BYTE}"
	else
		cat ${LOCAL_SOURCE} >> ${GLOBAL_FILE};write_ff ${GLOBAL_FILE} $((${LEN_TO_BYTE} - $(stat -c "%s" ${LOCAL_SOURCE})))
	fi
}
process_format_camera_header()
{
	make_camera_header "$1" "$(eval $2)" "$(unit_convert $3 B)"
}
process_format_file()
{
	cat $1 >> ${GLOBAL_FILE}
}
process_format_reserved()
{
	write_ff ${GLOBAL_FILE} "$(unit_convert $1 B)"
}
process_format_crcc_follow()
{
	local LOCAL_ADD_CRCC_FILE="crcc_follow_$$"

	#real checksum file
	dd if=${GLOBAL_FILE} of=${GLOBAL_SWAP_FILEA} bs=1 skip=$(unit_convert $1 B) count=$(unit_convert $2 B)
	get_checksum ${GLOBAL_SWAP_FILEA} > ${LOCAL_ADD_CRCC_FILE};cat ${GLOBAL_SWAP_FILEA} >> ${LOCAL_ADD_CRCC_FILE}

	#general global file
	dd if=${GLOBAL_FILE} of=${GLOBAL_SWAP_FILEA}.tmp bs=1 count=$(unit_convert $1 B);cat ${LOCAL_ADD_CRCC_FILE} >> ${GLOBAL_SWAP_FILEA}.tmp
	mv ${GLOBAL_SWAP_FILEA}.tmp ${GLOBAL_FILE};rm ${GLOBAL_SWAP_FILEA} ${LOCAL_ADD_CRCC_FILE}
}
process_format_crcc_precesing()
{
	#real checksum file
	dd if=${GLOBAL_FILE} of=${GLOBAL_SWAP_FILEA} bs=1 skip=$(unit_convert $1 B) count=$(unit_convert $2 B)
	get_checksum ${GLOBAL_SWAP_FILEA} >> ${GLOBAL_FILE};rm ${GLOBAL_SWAP_FILEA}
}
process_format_cmd()
{
	eval $1
}
process_format_cal_file_size_hex()
{
	echo_hex `endian_swap_8bit $(printf "%08x" $(stat -c "%s" $1))` >> "${GLOBAL_FILE}"
}
########################################################################################################

#all cfg config define
FILE_LIST=("bspinfo.cfg" "ccidinfo.cfg" "versioninfo.cfg" "extinfo.cfg" "databaseinfo.cfg" "efuseinfo.cfg" "updatekeyinfo.cfg" "factorykeyinfo.cfg" "vnextdatainfo.cfg" "adpicinfo.cfg" "camerapicinfo.cfg")
#List of files generated by each module defined
TMP_FILE_LIST=()

#arg1 6-bit format code eg:111111
#arg2 format name
#arg3 process func
add_lines_format()
{
	global_format_dict["$1"]="$2 $3"
}

#process_format
general_process_info()
{
	local LINE="$1"
	local AREA_NAME="$2"
	local LOCAL_FORMAT_HEX=0
	local LOCAL_ALL_VALUE LOCAL_FORMAT_BIT LOCAL_FORMAT_NAME LOCAL_VAL I
	local LOCAL_NAME="`echo $LINE | awk -F# '{print $1}' | grep -o '[^[:space:]]\+'`"

	# Check line format
	[ -z ${LOCAL_NAME} ] && return ${ERR_SOURCE_NAME_INVALID};echo -e "\e[1;36mProcessing\e[0m ${AREA_NAME}_${LOCAL_NAME}..."

	# process endline
	if [ "x${LOCAL_NAME}" = "xendline" ]; then
		write_ff ${GLOBAL_FILE} $((${GLOBAL_MODULE_SIZE} - $(stat -c "%s" ${GLOBAL_FILE})));return 1
	fi

	# Convert to true hex format
	for((I=2;I<=$((${GLOBAL_FORMAT_BIT_MAX}+1));I++))
	do
		LOCAL_FORMAT_BIT=$(echo "$LINE" | awk -F# "{print \$$I}" | grep -o '[^[:space:]]\+' &>/dev/null && echo 1 || echo 0)
		LOCAL_FORMAT_HEX=`echo $((${LOCAL_FORMAT_HEX}<<1 | ${LOCAL_FORMAT_BIT}))`
	done
	LOCAL_FORMAT_HEX=$(printf "%06d" `echo "obase=2;${LOCAL_FORMAT_HEX}"|bc`)

	# If the format does not have a name && exit
	LOCAL_FORMAT_NAME=$(echo "${global_format_dict[${LOCAL_FORMAT_HEX}]}" | awk '{print $1}')
	if [ -z "${LOCAL_FORMAT_NAME}" ]; then print_error "line format error!" ${ERR_LINE_FORMAT}; fi

	# get real val of format
	for((I=1;I<$((${GLOBAL_FORMAT_BIT_MAX}+1));I++))
	do
		if (echo "$LOCAL_FORMAT_HEX" | cut -c$I | grep -o '1') &>/dev/null; then
			LOCAL_VAL="${LOCAL_VAL}$(echo "$LINE" | awk -F# "{print \$$(($I + 1))}" | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//')@"
		fi
	done

	# process info
	if echo "${!global_format_dict[*]}" | grep -w "${LOCAL_FORMAT_HEX}" &>/dev/null; then
		# Scope of influence: "$*" Array
		OLD_IFS=$IFS;IFS=$'@\t\n'
		$(echo "${global_format_dict[${LOCAL_FORMAT_HEX}]}" | awk '{print $2}') ${LOCAL_VAL}${AREA_NAME}
		IFS=$OLD_IFS
	fi
}

#3.process_configinfo
process_configinfo()
{
	local configfile="$1"
	local name="$2"

	while read line;
	do
		# Blank started lines ignored
		if (echo "$line" | grep '^[[:space:]]*$\|^[[:space:]]*;') >/dev/null 2>&1; then
			continue
		fi

		# [global] section
		if (echo "$line" | grep '^[[:space:]]*\[global\][[:space:]]*$') >/dev/null 2>&1; then
			CUR_SECTION="global"
		# [source] section
		elif (echo "$line" | grep '^[[:space:]]*\[source\][[:space:]]*$') >/dev/null 2>&1; then
			CUR_SECTION="source"
		# [info] section
		elif (echo "$line" | grep '^[[:space:]]*\[info\][[:space:]]*$') >/dev/null 2>&1; then
			echo
			echo -e "\e[1;35m[${name}]\e[0m"
			CUR_SECTION="info"
		else
			case ${CUR_SECTION} in
				global)
					process_global "${line}"
					;;
				source)
					process_source "${line}"
					;;
				info)
					general_process_info "$line" "$name"
					;;
			esac
		fi
	done < "${configfile}"
}

GLOBAL_OUTPUT_RAWFILE="rawinfo.raw.img"
GLOBAL_OUTPUT_IMGFILE="rawinfo.img"
GLOBAL_CAMERA_FILE_DIR="$PWD/camerapic"
#0.check all cfg file exist
check_cfgfile()
{
	#clean rawinfo.img rawinfo.raw.img
	rm -f ${GLOBAL_OUTPUT_RAWFILE} $(dirname $PWD)/${GLOBAL_OUTPUT_IMGFILE} ${GLOBAL_CAMERA_FILE_DIR}/*.lz4 *.sig *.tmp >/dev/null 2>&1

	if [ $# -eq 1 ] && (ls *.cfg | grep "$1" >/dev/null 2>&1); then
		process_configinfo $1 $(echo $1 | awk -F. '{print $1}');print_notice "Processing Succeed!"
	elif [ $# -eq 0 ]; then
		for filename in ${FILE_LIST[@]};do [ ! -f ${filename} ] && print_error "${filename} does not exist!" ${ERR_NO_CFGFILE};done
	else
		print_error "parameter format error!" ${ERR_PARAM_WRONG}
	fi
}

#1.parse config
parse_config()
{
	local CFGFILE_NAME=""
	local FILE_NUM=0

	for cfgfile in ${FILE_LIST[@]}
	do
		CFGFILE_NAME="`echo ${cfgfile} | awk -F. '{print $1}'`"
		process_configinfo "${cfgfile}" "${CFGFILE_NAME}"

		TMP_FILE_LIST[$((FILE_NUM++))]="${GLOBAL_FILE}"
		IS_GLOBAL_PROCESSED=false
		GLOBAL_BLOCK_SIZE=""
		GLOBAL_FILE=""
	done
}

start=$(date +%s)
generate_rawinfo()
{
	for tmpfile in ${TMP_FILE_LIST[@]}
	do
		cat ${tmpfile} >> ${GLOBAL_OUTPUT_RAWFILE}
	done
}

img_to_simg()
{
	img2simg ${GLOBAL_OUTPUT_RAWFILE} ${GLOBAL_OUTPUT_IMGFILE}
	rm -f $(dirname $PWD)/${GLOBAL_OUTPUT_IMGFILE};mv ${GLOBAL_OUTPUT_IMGFILE} $(dirname $PWD);sync
	#clean
	for tmpfile in ${TMP_FILE_LIST[@]}
	do
		echo "clean ${tmpfile}";rm ${tmpfile}
	done
	end=$(date +%s)
	echo -e "\n\e[1;36m[generate rawinfo.img succeed $(( end - start ))s]\e[0m"
}

LINE_FORMAT=("010000-str-process_format_str" \
"100100-source-process_format_source" \
"001000-inputval-process_format_input_val" \
"000100-reserved-process_format_reserved" \
"101100-camera_header-process_format_camera_header" \
"100000-file-process_format_file" \
"011000-str_inputval-process_format_str_input_val" \
"110000-crcc_follow-process_format_crcc_follow" \
"010100-crcc_precesing-process_format_crcc_precesing" \
"000010-exec-process_format_cmd" \
"000001-cal_file_size-process_format_cal_file_size_hex")

init_config()
{
	local format_code format_name format_process_func

	for format in ${LINE_FORMAT[@]}
	do
		format_code=$(echo ${format} | awk -F- '{print $1}')
		format_name=$(echo ${format} | awk -F- '{print $2}')
		format_process_func=$(echo ${format} | awk -F- '{print $3}')

		add_lines_format "$format_code" "$format_name" "$format_process_func"
	done
}

#main
main()
{
	init_config
	check_cfgfile $1
	parse_config
	generate_rawinfo
	img_to_simg
}

# start
main $@

